import logging
import functools
from datetime import datetime
from hashlib import md5
from future.utils import raise_with_traceback
from common.user.models import User, UserToken, UserDescription
from django.forms.models import model_to_dict
from django.db.models import Q
from common.utils import id_generator
from common.utils.db import list_object
from django.db import transaction
from operator import and_, or_
from common.utils.maestro_exceptions import (
    MaestroException,
    ERROR_USER_NOT_EXISTS,
    ERROR_USER_PASSWORD_INCORRECT
)

_LOGGER = logging.getLogger(__name__)

_SALT = "V7$#g*BN".encode('utf-8')


def encode_password(passwd):
    return md5(passwd.encode('utf-8') + _SALT).hexdigest()


def create_user(username, password, role=''):
    user = User(
        username=username,
        password=encode_password(password),
        role=role,
        created_at=datetime.utcnow(),
        updated_at=datetime.utcnow()
    )
    user.save()


def get_role_user(merchant_role_id_list, user_filter_dct=None, exclude=False):
    if not merchant_role_id_list:
        return [], 0
    if not exclude:
        _filter = functools.reduce(
            or_, [Q(role__contains=',' + str(role_id) + ',') for role_id in merchant_role_id_list])
    else:
        _filter = functools.reduce(
            and_, [~Q(role__contains=',' + str(role_id) + ',') for role_id in merchant_role_id_list])
    users = User.objects.filter(_filter)
    if user_filter_dct and isinstance(user_filter_dct, dict):
        users = users.filter(**user_filter_dct)
    users = users.values()
    return users, len(users)


def update_user(info):
    with transaction.atomic():
        user = User.objects.select_for_update().filter(id=info['id']).first()
        if not user:
            raise_with_traceback(MaestroException(ERROR_USER_NOT_EXISTS))
        if 'password' in info:
            info['password'] = encode_password(info['password'])
        for k, v in info.items():
            setattr(user, k, v)
        user.save()
    return user


def list_users(query_dct, exclude_dct=None, disable_paginate=None):
    return list_object(query_dct, User, exclude_dct=exclude_dct, disable_paginate=disable_paginate)


def get_user(user_id):
    return User.objects.filter(id=user_id).first()


def login_user(username, password):
    user = User.objects.filter(username=username).first()
    if not user:
        raise MaestroException(ERROR_USER_NOT_EXISTS)
    if user.password != encode_password(password):
        raise MaestroException(ERROR_USER_PASSWORD_INCORRECT)
    user.last_logined_at = datetime.now()
    user.save()
    user_token = UserToken(
        token=id_generator.generate_uuid(),
        deleted=0,
        user_id=user.id,
        created_at=datetime.now(),
        updated_at=datetime.now()
    )
    user_token.save()
    user_info = model_to_dict(user)
    user_info['token'] = user_token.token
    user_info.pop('password', '')
    return user_info


def logout_user(user_id):
    UserToken.objects.filter(user_id=user_id).update(deleted=1)


def get_user_token(user_id, token):
    return UserToken.objects.filter(user_id=user_id, token=token).first()


def token_time_recode(user_id):
    UserToken.objects.filter(user_id=user_id).update(updated_at=datetime.now())


def get_user_description(user_id, merchant_id, supervisor_id):
    user_description = UserDescription.objects.filter(merchant_id=merchant_id,
                                                      user_id=user_id,
                                                      supervisor_id=supervisor_id).first()
    return user_description.description if user_description else ''


def create_or_update_user_description(user_id, merchant_id, supervisor_id, description):
    user_description, created = UserDescription.objects.get_or_create(
        merchant_id=merchant_id,
        user_id=user_id,
        supervisor_id=supervisor_id,
        defaults={
            'merchant_id': merchant_id,
            'user_id': user_id,
            'supervisor_id': supervisor_id,
            'description': description
        })
    if not created:
        user_description.description = description
        user_description.save()
    return {}


def delete_user(user_id):
    User.objects.filter(id=user_id).delete()
